﻿// ------------------------------------------------------------------------
// 项目名称：Canroc.Net 
// 版权归属：Canroc（https://gitee.com/canroc）
//
// 许可证信息
// Canroc.Net项目主要遵循 Apache 许可证（版本 2.0）进行分发和使用。许可证位于源代码树根目录中的 LICENSE-APACHE 文件。
//
// 使用条款：
// 1.使用本项目应遵守相关法律法规和许可证的要求。
// 2.不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动。
// 3.任何基于本项目二次开发而产生的一切法律纠纷和责任，我们不承担任何责任
//
// 免责声明
// 对于因使用本代码而产生的任何直接、间接、偶然、特殊或后果性损害，我们不承担任何责任。
//
// 其他重要信息
// Canroc.Net 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。
// ------------------------------------------------------------------------

using Canroc.Net.Application.ExternalCallback.Dto;
using Furion.JsonSerialization;
using System.Text.RegularExpressions;

namespace Canroc.Net.Application.ExternalCallback;

/// <summary>
///     外部回调服务
/// </summary>
[Route("callback")]
[AllowAnonymous]
public partial class ExternalCallbackAppService(
    IHttpContextAccessor httpContextAccessor,
    IJsonSerializerProvider jsonSerializerProvider) : IDynamicApiController
{
    [GeneratedRegex(@"\s")]
    public static partial Regex WhitespaceRegex();

    /// <summary>
    ///     支付回调
    /// </summary>
    /// <param name="input">请求参数</param>
    /// <returns></returns>
    [HttpPost("wechatpay/payment")]
    [UnifyProvider("wechatpay")]
    public async Task<string> WeChatPayPaymentCallbackAsync(WeChatPayPaymentCallbackInput input)
    {
        if (httpContextAccessor.HttpContext is null)
        {
            throw Oops.Oh("无效请求！");
        }

        #region 1. 验证签名 Wechatpay-Signature

        if (!httpContextAccessor.HttpContext.Request.Headers.TryGetValue("Wechatpay-Signature",
                out var signature) ||
            !httpContextAccessor.HttpContext.Request.Headers.TryGetValue("Wechatpay-Timestamp",
                out var timestamp) ||
            !httpContextAccessor.HttpContext.Request.Headers.TryGetValue("Wechatpay-Serial",
                out var serial) ||
            !httpContextAccessor.HttpContext.Request.Headers.TryGetValue("Wechatpay-Nonce",
                out var nonce))
        {
            throw Oops.Oh("无效请求，缺少必要参数！");
        }

        // 1.1 验证微信支付平台证书是否已经存在
        var weChatPayOptions = App.GetOptions<WeChatPayOptions>();
        var path = Path.Combine(AppContext.BaseDirectory, weChatPayOptions.CertificatePath, $"{serial}.pem");
        if (!File.Exists(path))
        {
            throw Oops.Oh("未找到此证书，验证失败！");
        }

        // 1.2 验证时间戳, 5分钟内有效
        var timestampLong = timestamp.ToString().ParseToLong();
        if (Math.Abs(DateTimeOffset.Now.ToUnixTimeSeconds() - timestampLong) > 300)
        {
            throw Oops.Oh("无效请求，时间戳已过期！");
        }

        // 1.3 验证签名
        var body = await httpContextAccessor.HttpContext.Request.ReadBodyContentAsync();
        var sign = $"{timestamp}\n{nonce}\n{WhitespaceRegex().Replace(body, "")}\n";
        if (!WeChatPayHelper.SHA256WithRSAVerifyData(sign, signature.ToString(), await File.ReadAllTextAsync(path)))
        {
            throw Oops.Oh("签名验证失败！");
        }

        #endregion

        #region 2. 解密回调数据

        if (input.Resource is null)
        {
            throw Oops.Oh("无效请求，缺少必要参数！");
        }

        // 解密回调数据 解密方式为 AEAD_AES_256_GCM
        var dataJson = WeChatPayHelper.AEAD_AES_256_GCMDecryption(weChatPayOptions.ApiV3Key,
            input.Resource.Ciphertext, input.Resource.Nonce, input.Resource.AssociatedData);
#pragma warning disable IDE0059 // 处理具体业务时解封
        // ReSharper disable once UnusedVariable
        var resource =
            jsonSerializerProvider.Deserialize<WeChatPayPaymentCallbackResource>(dataJson) ??
            throw Oops.Oh("解密回调数据失败！");
#pragma warning restore IDE0059 // 处理具体业务时解封

        #endregion

        // ReSharper disable once EmptyRegion

        #region 3. 实际业务处理

        #endregion

        return "接收成功";
    }

    /// <summary>
    ///     微信支付-退款回调
    /// </summary>
    /// <param name="input">请求参数</param>
    /// <returns></returns>
    [Route("wechatpay/refund")]
    [UnifyProvider("wechatpay")]
    public async Task<string> WeChatPayRefundCallbackAsync(WeChatPayRefundCallbackInput input)
    {
        if (httpContextAccessor.HttpContext is null)
        {
            throw Oops.Oh("无效请求！");
        }

        #region 1. 验证签名 Wechatpay-Signature

        if (!httpContextAccessor.HttpContext.Request.Headers.TryGetValue("Wechatpay-Signature",
                out var signature) ||
            !httpContextAccessor.HttpContext.Request.Headers.TryGetValue("Wechatpay-Timestamp",
                out var timestamp) ||
            !httpContextAccessor.HttpContext.Request.Headers.TryGetValue("Wechatpay-Serial",
                out var serial) ||
            !httpContextAccessor.HttpContext.Request.Headers.TryGetValue("Wechatpay-Nonce",
                out var nonce))
        {
            throw Oops.Oh("无效请求，缺少必要参数！");
        }

        // 1.1 验证微信支付平台证书是否已经存在
        var weChatPayOptions = App.GetOptions<WeChatPayOptions>();
        var path = Path.Combine(AppContext.BaseDirectory, weChatPayOptions.CertificatePath, $"{serial}.pem");
        if (!File.Exists(path))
        {
            throw Oops.Oh("未找到此证书，验证失败！");
        }

        // 1.2 验证时间戳, 5分钟内有效
        var timestampLong = timestamp.ToString().ParseToLong();
        if (Math.Abs(DateTimeOffset.Now.ToUnixTimeSeconds() - timestampLong) > 300)
        {
            throw Oops.Oh("无效请求，时间戳已过期！");
        }

        // 1.3 验证签名
        var body = await httpContextAccessor.HttpContext.Request.ReadBodyContentAsync();
        var sign = $"{timestamp}\n{nonce}\n{WhitespaceRegex().Replace(body, "")}\n";
        if (!WeChatPayHelper.SHA256WithRSAVerifyData(sign, signature.ToString(), await File.ReadAllTextAsync(path)))
        {
            throw Oops.Oh("签名验证失败！");
        }

        #endregion

        #region 2. 解密回调数据

        if (input.Resource is null)
        {
            throw Oops.Oh("无效请求，缺少必要参数！");
        }

        // 解密回调数据 解密方式为 AEAD_AES_256_GCM
        var dataJson = WeChatPayHelper.AEAD_AES_256_GCMDecryption(weChatPayOptions.ApiV3Key,
            input.Resource.Ciphertext, input.Resource.Nonce, input.Resource.AssociatedData);
#pragma warning disable IDE0059 // 处理具体业务时解封
        // ReSharper disable once UnusedVariable
        var resource =
            jsonSerializerProvider.Deserialize<WeChatPayRefundCallbackResource>(dataJson) ??
            throw Oops.Oh("解密回调数据失败！");
#pragma warning restore IDE0059 // 处理具体业务时解封

        #endregion

        // ReSharper disable once EmptyRegion

        #region 3. 实际业务处理

        #endregion

        return "接收成功";
    }
}